﻿/*
 *   NumericFunctions.cs
 *   Copyright (c) 2011 - BitFox Software and Ricardo Federico Villafañes
 *   Contact: federvillaf@hotmail.com
 *   
 *   Licensing: This product is covered by Microsoft Reciprocal License (MS-RL)
 *              see https://bitfoxeval.codeplex.com/license
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.CodeAnalysis;

namespace BitFox.Evaluator.Functions
{
    /// <summary>
    /// Built-in numeric functions for the evaluator.
    /// </summary>
    public static class NumericFunctions
    {
        static decimal[] rounddiv = {1m, 10m, 100m, 1000m, 10000m, 100000m, 1000000m, 10000000m, 100000000m,
                                        1000000000m, 10000000000m};

        /// <summary>
        /// Return the positive value that corresponds to passed arg
        /// </summary>
        /// <param name="decVal">The value to which to remove the sign</param>
        /// <returns></returns>
        static public decimal Abs(decimal decVal)
        {
            return Math.Abs(decVal);
        }

        /// <summary>
        /// Returns the arc cosine of a specified numeric expression.
        /// </summary>
        /// <remarks>This trigonometric function returns the arc cosine of a numeric expression in radians.
        /// Use RTOD() to convert radians to degrees.</remarks>
        /// <param name="arcv">The angle for which to return the arc cosine. It can range from +1 to -1.</param>
        /// <returns>The arc cosine of <paramref name="arcv"/>. It can range from 0 through PI (3.141592654).</returns>
        static public decimal Acos(decimal arcv)
        {
            return FoxConvert.ToDecimal(Math.Acos(decimal.ToDouble(arcv)));
        }

        /// <summary>
        /// Returns the arc sine of a specified numeric expression.
        /// </summary>
        /// <remarks>This trigonometric function returns the arc sine of a numeric expression in radians.
        /// Use RTOD() to convert radians to degrees.</remarks>
        /// <param name="arcv">The angle for which to return the arc sine. It can range from +1 to -1.</param>
        /// <returns>The arc cosine of <paramref name="arcv"/>. It can range from -PI/2 through +PI/2.</returns>
        static public decimal Asin(decimal arcv)
        {
            return FoxConvert.ToDecimal(Math.Asin(decimal.ToDouble(arcv)));
        }

        /// <summary>
        /// Returns the arc tangent of a specified numeric expression.
        /// </summary>
        /// <remarks>This trigonometric function returns the arc tangent of a numeric expression in radians.
        /// Use RTOD() to convert radians to degrees.</remarks>
        /// <param name="arcv">The angle for which to return the arc tangent. It can be any number.</param>
        /// <returns>The arc tangent of <paramref name="arcv"/>. It can range from -PI/2 through +PI/2.</returns>
        static public decimal Atan(decimal arcv)
        {
            return FoxConvert.ToDecimal(Math.Atan(decimal.ToDouble(arcv)));
        }

        /// <summary>
        /// Returns the arc tangent of a specified sine and cosigne expressions.
        /// </summary>
        /// <remarks>This trigonometric function returns the arc tangent of two expressions, representing the
        /// sine and cosigne of a given angle.</remarks>
        /// <param name="coordX">Specify the X coordinate (SINE) of the angle. The value of x/y must be between -PI and +PI</param>
        /// <param name="coordY">Specify the Y coordinate (COSINE) of the angle</param>
        /// <returns>The angle for which the corresponding sine and cosine were given.</returns>
        static public decimal Atn2(decimal coordY, decimal coordX)
        {
            return FoxConvert.ToDecimal(Math.Atan2(decimal.ToDouble(coordY), decimal.ToDouble(coordX)));
        }

        /// <summary>
        /// Returns the nearest integer that is greater than or equal to the specified numeric expression.
        /// </summary>
        /// <remarks>
        /// CEILING() returns the nearest integer that is greater than or equal to a numeric expression.
        /// CEILING rounds a positive number with a fractional portion up to the next highest integer, and rounds a 
        /// negative number with a fractional portion up to the integer portion of the number.</remarks>
        /// <param name="val">The numeric expression to round.</param>
        /// <returns>The neareset integer that is greather than or equal to a numeric expression.</returns>
        static public decimal Ceiling(decimal val)
        {
            return decimal.Ceiling(val);
        }

        /// <summary>
        /// Returns the cosine of a numeric expression.
        /// </summary>
        /// <remarks>This trigonometric function returns the cosine of a numeric expression.
        /// To convert an angle from degrees to radians, use DTOR().
        /// </remarks>
        /// <param name="angle">The angle for which to return the cosine. It can be any value.</param>
        /// <returns>The cosine of <paramref name="angle"/>. It ranges between -1 and +1.</returns>
        static public decimal Cos(decimal angle)
        {
            return FoxConvert.ToDecimal(Math.Cos(decimal.ToDouble(angle)));
        }

        /// <summary>
        /// Returns the natural logarithm (base E) of the specified numeric expression.
        /// </summary>
        /// <remarks>The mathematical function LOG() returns the natural logarithm of a numeric expression.  
        /// The base for the natural logarithm is the constant e.  
        /// </remarks>
        /// <param name="val">The value for which to return the natural logarithm.</param>
        /// <returns>The natural logarithm of <paramref name="val"/>.</returns>
        static public decimal Log(decimal val)
        {
            return FoxConvert.ToDecimal(Math.Log(decimal.ToDouble(val)));
        }

        /// <summary>
        /// Returns the common logarithm (base 10) of the specified numeric expression.
        /// </summary>
        /// <remarks>The mathematical function LOG10() returns the natural logarithm of a numeric expression.  
        /// The base for the natural logarithm is the constant 10.  
        /// </remarks>
        /// <param name="val">The value for which to return the common logarithm.</param>
        /// <returns>The common logarithm of <paramref name="val"/>.</returns>
        static public decimal Log10(decimal val)
        {
            return FoxConvert.ToDecimal(Math.Log10(decimal.ToDouble(val)));
        }

        /// <summary>
        /// Returns the sine of a numeric expression.
        /// </summary>
        /// <remarks>This trigonometric function returns the sine of a numeric expression that represents an angle
        /// in radians. To convert an angle from degrees to radians, use DTOR().
        /// </remarks>
        /// <param name="angle">The angle for which to return the sine, expressed in radians. 
        /// It can be any value.</param>
        /// <returns>The sine of <paramref name="angle"/>. It ranges between -1 and +1.</returns>
        static public decimal Sin(decimal angle)
        {
            return FoxConvert.ToDecimal(Math.Sin(decimal.ToDouble(angle)));
        }

        /// <summary>
        /// Returns the square root of the specified numeric expression.
        /// </summary>
        /// <remarks>SQRT() returns the square root of a numeric expression.</remarks>
        /// <param name="val">The value for which to return the square root. It cannot be a negative number.</param>
        /// <returns></returns>
        static public decimal Sqrt(decimal val)
        {
            return FoxConvert.ToDecimal(Math.Sqrt(decimal.ToDouble(val)));
        }

        /// <summary>
        /// Returns the tangent of a numeric expression.
        /// </summary>
        /// <remarks>This trigonometric function returns the tangent of a numeric expression that represents an angle
        /// in radians. To convert an angle from degrees to radians, use DTOR().
        /// </remarks>
        /// <param name="angle">The angle for which to return the tangent, expressed in radians. 
        /// It can be any value.</param>
        /// <returns>The tangent of <paramref name="angle"/>.</returns>
        static public decimal Tan(decimal angle)
        {
            return FoxConvert.ToDecimal(Math.Tan(decimal.ToDouble(angle)));
        }

        /// <summary>
        /// Returns the nearest integer that is less than or equal to the specified numeric expression.
        /// </summary>
        /// <remarks>
        /// FLOOR() returns the nearest integer that is less than or equal to a numeric expression.
        /// FLOOR() rounds a positive number with a fractional portion down to the integer portion of the number, 
        /// and rounds a negative number with a fractional portion down to the next lowest integer.
        /// </remarks>
        /// <param name="val">The value for which to return the nearest integer.</param>
        /// <returns>The rounded value of <paramref name="val"/>.</returns>
        static public decimal Floor(decimal val)
        {
            return FoxConvert.ToDecimal(Math.Floor(decimal.ToDouble(val)));
        }

        /// <summary>
        /// Returns the integer portion of a numeric expression.
        /// </summary>
        /// <remarks>INT() evaluates a numeric expression and returns the integer part of the expression.</remarks>
        /// <param name="val">The number for which to return the integer portion.</param>
        /// <returns>The integer portion of <paramref name="val"/>.</returns>
        static public decimal Int(decimal val)
        {
            return FoxConvert.ToDecimal((int)val);
        }

        /// <summary>
        /// Divides one numeric expression by another numeric expression and returns the remainder.
        /// </summary>
        /// <remarks>
        /// MOD() (modulus) divides two numeric expressions and returns the remainder.  
        /// MOD() and the % operator return identical results.
        /// </remarks>
        /// <param name="num">The dividend. 
        /// The number of decimal places in this parameter determines the number of decimals places in the result.</param>
        /// <param name="divisor">The divisor.
        /// A positive number is returned if this parameter is positive, and a negative number is returned if it is negative.</param>
        /// <returns>The indicated remainder of the division between <paramref name="num"/> and <paramref name="divisor"/>.</returns>
        static public decimal Mod(decimal num, decimal divisor)
        {
            // Algorithm complicates because FOXPRO is able to get the remainder from non-integer values.
            // It should raise an error in this case, but I want to be as compatible as possible...
            bool negative = (num < 0);
            decimal result = (decimal)(num % divisor);
            if (negative) return divisor + result;
            return result;
        }

        /// <summary>
        /// Returns the numeric constant PI.
        /// </summary>
        /// <remarks>
        /// This mathematical function returns the numeric constant pi (3.141592), the ratio of the circumference of a circle 
        /// to its diameter.</remarks>
        /// <returns>The value of PI, up to 8 decimals places.</returns>
        static public decimal Pi()
        {
            return FoxConvert.ToDecimal(Math.PI);
        }

        /// <summary>
        /// Return a numeric expression rounded to a specified number of decimal places.
        /// </summary>
        /// <remarks>ROUND() returns the value of a numeric expression rounded to a specified number of decimal places.  
        /// The value returned by ROUND() has the same number of decimal places as <paramref name="val"/>.</remarks>
        /// <param name="val">The value to round.</param>
        /// <param name="num">
        /// The number of decimal places to round.
        /// If this parameter is negative, ROUND() returns a whole number containing <paramref name="num"/> zeros to the left of the 
        /// decimal point. For example, if <paramref name="num"/> is -2 the first and second digits to the left of the decimal point
        /// in the value are zero. 
        /// </param>
        /// <returns>The result of rounding <paramref name="val"/> to <paramref name="num"/> decimal places.</returns>
        static public decimal Round(decimal val, decimal num)
        {
            // We cannot use 'decimal.Round(...)' to do rounding because in FOXPRO  ROUND(0.05, 1)=0.1, when
            // decimal.Round(0.05,1) returns 0 (it considers a half as not a trigger, while foxpro yes)

            // Determines the multiplier to use
            decimal mult = 1;
            int xnum = (int)Math.Abs(num);
            if (num < 10)
                mult = rounddiv[xnum];
            else
            {
                for (int inum = xnum; inum > 0; inum--)
                    mult *= 10m;
            }

            // If 'num' is negative, 'num' digits at left of decimal dot must be zeroed
            if (num < 0)
            {
                int temp = (int)(val / mult);
                return ((decimal)temp) * mult;
            }
            
            // Rounding algo, as usual
            decimal dpart = val * mult;
            if ((dpart - (int)dpart) >= 0.5m)
                dpart += 1;
            return (decimal)((int)dpart) / mult;
        }

        /// <summary>
        /// Returns a numeric value of 1, -1 or 0 depending on the sign of a specified numeric expression.
        /// </summary>
        /// <remarks>
        /// SIGN() returns a numeric value of 1, -1 or 0 depending on whether the specified numeric expression evaluates 
        /// to a positive, negative, or 0 value.</remarks>
        /// <param name="val">The number for which the sign is detected.</param>
        /// <returns>
        /// SIGN() returns 1 if <paramref name="val"/> evaluates to a positive number, -1 if <paramref name="val"/> evaluates to 
        /// a negative number, and 0 if <paramref name="val"/> evaluates to 0.</returns>
        static public decimal Sign(decimal val)
        {
            if (val >= 0) return 1m;
            return -1m;
        }

        /// <summary>
        /// Returns the future value of an investment.
        /// </summary>
        /// <remarks>
        /// FV() computes the future value of a series of constant periodic payments earning fixed compound interest.
        /// The future value is the total of all payments and the interest.</remarks>
        /// <param name="pv">The constant periodic payment (which can be negative or positive).</param>
        /// <param name="rate">	The periodic interest rate. If the interest rate is annual but the payments are made monthly, 
        /// divide the annual interest rate by 12.</param>
        /// <param name="dnum">Specifies the number of periods over which payments are made.
        /// FV() assumes that the periodic payments are made at the end of each period.</param>
        /// <returns>The future value of the indicated investment.</returns>
        static public decimal FV(decimal pv, decimal rate, decimal dnum)
        {
            decimal result = 0;
            dnum = Math.Abs(dnum);
            if (dnum == 0 || pv == 0 || rate == 0) return 0m;

            double oRate = decimal.ToDouble(1 + Math.Abs(rate));
            for (int num = (int)(dnum - 1); num >= 0; num--)
                result += pv * FoxConvert.ToDecimal(Math.Pow(oRate, (double)num));
            return result;
        }

        /// <summary>
        /// Converts a number that express DEGREES to its equivalent in RADIANS
        /// </summary>
        /// <param name="degrees">The number in degrees to be converted</param>
        /// <returns>The result in radians</returns>
        static public decimal Dtor(decimal degrees) 
        {
            double res = (double)degrees * Math.PI / 180;
            return FoxConvert.ToDecimal(res);
        }

        /// <summary>
        /// Converts a number that express RADIANS to its equivalent in DEGREES
        /// </summary>
        /// <param name="radians">The number in radians to be converted</param>
        /// <returns>The result in degrees</returns>
        static public decimal Rtod(decimal radians)
        {
            double res = (double)radians * 180 / Math.PI;
            return FoxConvert.ToDecimal(res);
        }

        /// <summary>
        /// Returns the value of e^x where x is a specified numeric expression.
        /// </summary>
        /// <param name="val">This numeric expression is the exponent, val, in the exponential equation e^val.</param>
        /// <returns>The result of exponentiation</returns>
        /// <remarks>The mathematical function EXP() returns the value of e^val where val is a numeric expression.
        /// The value of e, the base of natural logarithms, is approximately 2.71828.
        /// The number of decimal places displayed by EXP() is specified with the property 'Decimals' of Evaluator's context.</remarks>
        public static decimal Exp(decimal val)
        {
            return FoxConvert.ToDecimal(Math.Exp((double) val));
        }

        /// <summary>
        /// Not implemented
        /// </summary>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Performance", "CA1801")]
        static public decimal Payment(decimal val)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Not implemented
        /// </summary>
        /// <returns></returns>
        static public decimal Pv()
        {
            throw new NotImplementedException();
        }
    }
}